package dalgo.main;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.LinkedList;

import uniandes.cupi2.collections.arbol.arbolBinario.ArbolBinario;
import uniandes.cupi2.collections.arbol.arbolBinario.NodoArbolBinario;
import dalgo.utils.Utils;
import dalgo.reglas.Aislamiento;
import dalgo.reglas.Atraccion;
import dalgo.reglas.Coleccion;
import dalgo.reglas.Regla;

public class Main {

	private static final String RUTA_LECTURA = "./resources/equ.in";
	private static final String RUTA_ESCRITURA = "C://DAlgoProy/";

	/**
	 * "Arreglo" que modela la agenda con la que se realizan las expansiones de
	 * estados del grafo imaginario. Son los elementos por expandir
	 */
	private LinkedList<Estado> agenda = new LinkedList<Estado>();

	/**
	 * Sirve para hacer la busqueda de los nodos a la hora de la construccion de
	 * los arboles
	 */
	private LinkedList<Estado> marcados = new LinkedList<Estado>();

	/**
	 * Contador para los IDs de los nodos de cada arbol
	 */
	private static int contadorNodos = -1;

	/**
	 * ID de la ecuacion que se esta resolviendo
	 */
	private String idEcuacion;

	/**
	 * Campo que modela la variable a resolver en esta ecuacion
	 */
	private static String variable;

	/**
	 * Estado actual. este es el nodo del grafo imaginario que se esta evaluando
	 * actualmente
	 */
	private Estado estadoActual;
	
	private String ecuacion;
	private String tiempo;

	private Main() {

		// Leer archivo
		leerArchivo();

		// Generar estado inicial
		// meterlo en agenda

		// tomar primer elemento de agenda, revisar fin
		// y expandirlo segun reglas, y
boolean termino = false;
		do {
			termino = expandirEstado();
		} while (!agenda.isEmpty()&&!termino);

		// marcarlo
		// ordenar agenda
		// repetir

		// generar archivo
		escribirArchivo();

	}

	/**
	 * Metodo que verifica que el nodo actual (del grafo imaginario) es la
	 * solucion de la ecuacion. Verifica que el nodo derecho o izquierdo tienen
	 * unicamente la variable a resolver
	 * 
	 * @return <code>false</code> si no es el nodo solucion, <code>true</code>
	 *         de lo contrario
	 */
	private boolean finalizado() {

		ArbolBinario<String> a = estadoActual.getArbol();

		if (a.darRaiz().darIzquierdo().darElemento().equals(variable))
			return true;
		else if (a.darRaiz().darDerecho().darElemento().equals(variable))
			return true;
		else
			return false;
	}

	/**
	 * Hace las transformaciones necesarias al nodo actual e inserta dichas
	 * transformaciones en la agenda para fururas expansiones.
	 * 
	 */
	private boolean expandirEstado() {

		estadoActual = agenda.poll();

		LinkedList<Estado> expansion = new LinkedList<Estado>();

		if (!finalizado()) {

			Regla regla = new Atraccion();

			LinkedList<Estado> temp = regla.aplicarRegla(
					Utils.copiarArbol(estadoActual.getArbol()),
					estadoActual.getId());

			if (temp != null && !temp.isEmpty()) {
				expansion.addAll(temp);
			}

			if (expansion.isEmpty()) {

				regla = new Coleccion();

				temp = regla.aplicarRegla(
						Utils.copiarArbol(estadoActual.getArbol()),
						estadoActual.getId());

				if (temp != null && !temp.isEmpty()) {
					expansion.addAll(temp);
				}
			}
			if (expansion.isEmpty()) {

				regla = new Aislamiento();
				expansion.addAll(regla.aplicarRegla(
						Utils.copiarArbol(estadoActual.getArbol()),
						estadoActual.getId()));

			}

			for (int i = 0; i < expansion.size(); i++) {

				meterEnAgenda(expansion.poll());
			}

			marcados.add(estadoActual);

			return false;
		} else {
			return true;
		}

	}

	/**
	 * Inserta un nodo en la agenda para su futura expansion, esta insercion es
	 * dada por el valor de la funcion heuristica de el nodo dado
	 * 
	 * @param e
	 *            estado a insertar en la agenda
	 * @see Estado#calcularHeuristica()
	 */
	private void meterEnAgenda(Estado e) {
		boolean insertado = false;
		if (agenda.isEmpty())
			agenda.add(e);
		else
			for (int i = 0; i < agenda.size() && !insertado; i++) {

				if (agenda.get(i).compareTo(e) > 0) {
					agenda.add(i, e);
					insertado = true;
				}
			}
	}

	/**
	 * Metodo que busca el nodo padre (de arbol), usado a la hora de construir
	 * el arbol del algun nodo (de arbol) nuevo.
	 * 
	 * @param idPadre
	 *            id del padre a buscar en la lista de nodos procesados
	 *            (marcados)
	 * @return el estado que contiene como raiz el padre a buscar
	 */
	private Estado buscarId(int idPadre) {

		boolean encontro = false;
		for (int i = 0; i < marcados.size() && !encontro; i++) {

			if (marcados.get(i).getId() == idPadre) {
				encontro = true;
				return marcados.get(i);
			}
		}
		return null;
	}

	/**
	 * Lee el archivo con la ecuacion de entrada, esta es la ecuacion a resolver
	 * con el programa. Construye el primer nodo (del grafo imaginario) a
	 * partidr de dicha ecuacion
	 * 
	 * <br>
	 * <br>
	 * <b>Precondicion</b> El archivo tiene el formato correcto, y la ecuacion
	 * esta formada correctamente
	 */
	private void leerArchivo() {
		try {

			FileReader fr = new FileReader(new File(RUTA_LECTURA));

			BufferedReader br = new BufferedReader(fr);


			idEcuacion = br.readLine();
			ecuacion = br.readLine();

			ArbolBinario<String> primerArbol = new ArbolBinario<String>();
			String[] arregloEcuacion = ecuacion.split(" ");

			NodoArbolBinario<String> raiz = new NodoArbolBinario<String>(
					arregloEcuacion[arregloEcuacion.length - 1]);

			primerArbol.definirRaiz(raiz);
			NodoArbolBinario<String> nodoAnterior = raiz;

			LinkedList<NodoArbolBinario<String>> listaPendiente = new LinkedList<NodoArbolBinario<String>>();

			for (int i = arregloEcuacion.length - 2; i >= 0; i--) {
				NodoArbolBinario<String> nodo = new NodoArbolBinario<String>(
						arregloEcuacion[i]);
				if (Utils.esUnario(nodoAnterior.darElemento())) {
					nodoAnterior.encadenarDerecho(nodo);
					nodoAnterior = nodo;
				}

				else if (Utils.esBinario(nodoAnterior.darElemento())) {
					nodoAnterior.encadenarDerecho(nodo);
					listaPendiente.addFirst(nodoAnterior);
					nodoAnterior = nodo;
				} else {
					nodoAnterior = listaPendiente.pollFirst();
					nodoAnterior.encadenarIzquierdo(nodo);
					nodoAnterior = nodo;
				}
			}

			variable = br.readLine();
			tiempo = br.readLine();
			br.close();

			Estado primerEstado = new Estado(primerArbol, contadorNodos,
					"--Estado inicial--");
			agenda.add(primerEstado);

		} catch (FileNotFoundException e) {

			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}

	}

	/**
	 * Escribe el archivo con las transformaciones realizadas para llegar a la
	 * solucion
	 */
	private void escribirArchivo() {
		int i = estadoActual.getId();
		Estado e = estadoActual;
		LinkedList<Estado> listaResultado = new LinkedList<Estado>();
		listaResultado.addFirst(estadoActual);
		while (i != 0) {
			e = buscarId(e.getIdPadre());
			listaResultado.addFirst(e);
			i = e.getId();

		}
		
		
		File f = new File(RUTA_ESCRITURA);
		f.mkdirs();
		f= new File(RUTA_ESCRITURA+"GP_07_"+idEcuacion+".out");
		PrintWriter pw;
		try {
			pw = new PrintWriter(f);
			pw.println(idEcuacion);
			pw.println(ecuacion);
			pw.println(getVariable());
			pw.println(tiempo);
			
			
			pw.println("--------------------");
			for (int j = 0; j < listaResultado.size(); j++) {

				pw.println("[" + (j) + "]  "
						+ listaResultado.get(j).getNombreTransicion());
				listaResultado.get(j).imprimir(pw);

			}
			pw.println("--------------------");

			pw.close();
			
		} catch (FileNotFoundException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}

		
	}

	/**
	 * Retorna el campo que modela la variable a resolver
	 * 
	 * @return la variable que modela la variable a resolver
	 */
	public static String getVariable() {
		return variable;
	}

	/**
	 * Metodo usado para la definicion de IDs para los nodos (de arbol) de
	 * cualquiera de los arboles generados por el programa
	 * 
	 * @return la cuenta de los nodos (de arbol) que se han generado hasta el
	 *         momento, +1
	 */
	public static int darCuentaNodos() {

		contadorNodos++;

		return contadorNodos;
	}

	/**
	 * @param args
	 */
	public static void main(String[] args) {

		new Main();
	}

}
